This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

This will help us focus on the goal of the code (understanding stats better), instead of the code itself, which is a means to an end.

You can take this notebook with you, which will provide templates to execute various analyses.

Install some extra packages (if needed)

install.packages("colorbrewer")
Warning in install.packages :
  package ‘colorbrewer’ is not available (for R version 3.5.1)

Call packages

library(tidyverse)
library(modelr)
library(broom)

Attaching package: ‘broom’

The following object is masked from ‘package:modelr’:

    bootstrap
library(epiR)
library(janitor)

Load data

We’ll be using a table of simplified school-level data regarding the racial makeup of schools and their scores on the PARCC standardized test.

(It’s outside our purview today, but of course normally we would use best practice and study the data dictionary/documentation before we do anything else).

schools_data <- read_csv("ILschools.csv")
Parsed with column specification:
cols(
  id = col_character(),
  nameSCH = col_character(),
  nameDIST = col_character(),
  city = col_character(),
  county = col_character(),
  CHI = col_logical(),
  type = col_character(),
  pctWhite = col_double(),
  pctBlack = col_double(),
  pctHisp = col_double(),
  pctAsian = col_double(),
  pctPCISL = col_double(),
  pctNativ = col_double(),
  pctMulti = col_double(),
  pctLowInc = col_double(),
  PARCCpct = col_double()
)

The readr package decisions seem reasonable. Let’s have a look at the top of our table. Scroll through to the right, paying special attention to our percentage columns.

head(schools_data)

Fix column headers

The janitor package fixes a lot of common data hygeine problems. In this case, we’ll change these hideous and inconsistent column names to conform with R conventions

schools_data <- schools_data %>% clean_names()
head(schools_data)

Learn a bit about our columns from the get-go

summary(schools_data)
      id              name_sch          name_dist        
 Length:3796        Length:3796        Length:3796       
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         
     city              county             chi         
 Length:3796        Length:3796        Mode :logical  
 Class :character   Class :character   FALSE:3166     
 Mode  :character   Mode  :character   TRUE :630      
                                                      
                                                      
                                                      
                                                      
     type             pct_white        pct_black     
 Length:3796        Min.   :  0.00   Min.   :  0.00  
 Class :character   1st Qu.: 17.88   1st Qu.:  1.00  
 Mode  :character   Median : 62.10   Median :  3.50  
                    Mean   : 54.29   Mean   : 17.84  
                    3rd Qu.: 88.70   3rd Qu.: 17.30  
                    Max.   :100.00   Max.   :100.00  
                                                     
    pct_hisp       pct_asian       pct_pcisl      
 Min.   : 0.00   Min.   : 0.00   Min.   :0.00000  
 1st Qu.: 2.50   1st Qu.: 0.00   1st Qu.:0.00000  
 Median : 8.60   Median : 0.80   Median :0.00000  
 Mean   :20.29   Mean   : 3.72   Mean   :0.09049  
 3rd Qu.:27.02   3rd Qu.: 3.40   3rd Qu.:0.10000  
 Max.   :99.50   Max.   :85.20   Max.   :5.00000  
                                                  
   pct_nativ         pct_multi       pct_low_inc    
 Min.   : 0.0000   Min.   : 0.000   Min.   :  0.00  
 1st Qu.: 0.0000   1st Qu.: 1.100   1st Qu.: 28.60  
 Median : 0.0000   Median : 2.700   Median : 49.00  
 Mean   : 0.3381   Mean   : 3.429   Mean   : 51.68  
 3rd Qu.: 0.3000   3rd Qu.: 4.700   3rd Qu.: 75.40  
 Max.   :48.9000   Max.   :39.600   Max.   :100.00  
                                                    
   parc_cpct    
 Min.   : 0.00  
 1st Qu.:19.50  
 Median :31.40  
 Mean   :33.63  
 3rd Qu.:46.20  
 Max.   :97.00  
 NA's   :811    

We can see that the various columns have very different means. We can also see that the means and medians can be very different. This indicates to us that the distributions are different, and not normal.

Exploring distributions with ggplot

It’s a very powerful graphics library in R. We won’t dwell too much on ggplot syntax here, but I encourage you to keep working on it.

First, let’s look at what’s known as a normal distribution.

set.seed(1)
df <- data.frame(PF = 10*rnorm(10000))
ggplot(df, aes(x = PF)) + 
    geom_histogram(aes(y =..density..),
                   breaks = seq(-50, 50, by = 5), 
                   colour = "black", 
                   fill = "deepskyblue") +
stat_function(fun = dnorm, args = list(mean = mean(df$PF), sd = sd(df$PF)))

print(paste0("mean: ", round(mean(df$PF),0)))
[1] "mean: 0"
print(paste0("median: ", round(median(df$PF),0)))
[1] "median: 0"

In this distribution, the mean (aka ‘average’) and the median are equal, and both sides are symmetrical around them. But this is unusual in the real world, so looking at distributions can tell us a lot about

Let’s start with the distribution of test scores, which we’ll make with ggplot.

# First tell ggplot which data and variable to use
c <- ggplot(schools_data, aes(parc_cpct))
# Then we tell ggplot which viz to make
c + geom_histogram(binwidth = 10)

What does this tell us about the distribution of school test-passing rates? This is called skew.

Next let’s look at the distribution of the percent black variable

# First tell ggplot which data and variable to use
c <- ggplot(schools_data, aes(pct_black))
# Then we tell ggplot which viz to make
c + geom_histogram(binwidth = 10)

What does this shape tell us about schools in Illinois?

What are some other areas of life where we might find this distribution?

You try: make a histogram of the distribution of pct_low_inc

Looking at relationships between two variables

Let’s explore the relationship between the percent of a school’s students who are low-income, and the percent who are proficient.

First, let’s practice filtering for just elementary schools.

elementary_schools <- schools_data %>% filter(type == "ELEMENTARY")

Now let’s plot the relationship between the percent of a school’s students who are low-income and the percent who achieved proficiency on the PARCC exam.

a <- ggplot(elementary_schools, aes(pct_low_inc, parc_cpct))
a + geom_point(color = "turquoise", alpha = .6)

We can see that in general a higher rate of low-income students at a school is associated with a lower rate of passing the exam.

Let’s formalize this by fitting a line.

a + geom_point(color = "turquoise", alpha = .6) + geom_smooth(method = lm)

low_inc_mod <- lm(parc_cpct ~ pct_low_inc, data = elementary_schools)
summary(low_inc_mod)

Call:
lm(formula = parc_cpct ~ pct_low_inc, data = elementary_schools)

Residuals:
    Min      1Q  Median      3Q     Max 
-45.690  -7.288  -0.824   6.851  68.457 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 60.670894   0.504760  120.20   <2e-16 ***
pct_low_inc -0.492279   0.008203  -60.01   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 11.45 on 2229 degrees of freedom
  (175 observations deleted due to missingness)
Multiple R-squared:  0.6177,    Adjusted R-squared:  0.6175 
F-statistic:  3602 on 1 and 2229 DF,  p-value: < 2.2e-16

Remember y = mx + b ? What does the output above tell us?

You try: Run a linear regression that shows the relationship between the percent white and the percent who passed the exam. What do we learn from this? How do we put it into words?

Multiple regression

Does a school being in Chicago affect these results? First, let’s consider this visually.

a <- ggplot(elementary_schools, aes(pct_low_inc, parc_cpct, color = chi))
a + geom_point(alpha = .5)

a + geom_point(alpha = .5) + geom_smooth(method = "lm")

Let’s run a new regression that includes the ‘chi’ variable. And then summarize output.

multi_mod <- lm(parc_cpct ~ pct_low_inc + chi, data = elementary_schools)
summary(multi_mod)

Call:
lm(formula = parc_cpct ~ pct_low_inc + chi, data = elementary_schools)

Residuals:
    Min      1Q  Median      3Q     Max 
-46.589  -7.594  -0.537   7.043  72.682 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 62.070922   0.499632  124.23   <2e-16 ***
pct_low_inc -0.548532   0.009067  -60.50   <2e-16 ***
chiTRUE      8.773219   0.688236   12.75   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 11.06 on 2228 degrees of freedom
  (175 observations deleted due to missingness)
Multiple R-squared:  0.6437,    Adjusted R-squared:  0.6434 
F-statistic:  2013 on 2 and 2228 DF,  p-value: < 2.2e-16

Looking better, but the output is still pretty ugly. We can make it more useful using the broom packaage.

tidy(multi_mod)

Now it’s a data frame, instead of that weird model object.

You practice. Run a linear model where our predictors are pct_white and chi. Then clean up your output using broom.

If we feed our original data frame and our model through the augment() function, it will calculate fitted values for us.

A fitted, or predicted, value is the percent PARCC proficiency our model suggests would be expected for this school given the terms in our model (in this case the percent of a school’s students who are low-income and whether the school is in Chicago).

mod_grid <- lm(parc_cpct ~ pct_low_inc + chi, data = schools_data, na.action = "na.exclude") %>% augment(multi_mod, elementary_schools) 
View(head(mod_grid,20))

The difference between the fitted value and the actual value can be interpreted as whether a school is over- or under-performing. We can plot this.

To keep our plot from being too busy, let’s filter out values that are not outliers.

mod_grid <- mod_grid %>% mutate(residual = .fitted - parc_cpct) %>% filter(residual < -20 | residual > 20)
ggplot(mod_grid, aes(x = pct_low_inc, y = parc_cpct)) +
  geom_segment(aes(xend = pct_low_inc, yend = .fitted), alpha = .2) +
  geom_point(aes(color = residual),alpha = .8) +
  scale_color_gradient2(low = "blue", mid = "white", high = "red") +
  guides(color = FALSE) +
  geom_point(aes(y = .fitted)) +
  facet_grid(~ chi)

Looks like we’ve got some schools with unexpected test scores!

Let’s find them.

View(mod_grid %>% arrange(residual))

Story language:

Eisenhower Elementary outscored what would be expected for a similar school with mostly low-income students by 73 percentage points.

Logistic regression

Add a column for ‘good school’ where more than half of the students have passed the test.

Now let’s use more than one variable to predict whether a school is “good”.

We can use our summary function again to see the output of our regression.

Let’s unpack what our results mean.

You try: Let’s create a variable indicating whether a school is in Cook County or not, called ‘cook’.

Now let’s run a logistic regression with cook and pct_black as our predictors and good_school as our dependent variable, then summarize.

What do we learn?

You try: Run a logistic regression with chi and pct_white as our predictors, and good_school as our dependent variable. Then summarize.

What do we learn?

What are some other questions we could answer with a logistic regression?

Non-linear relationships

Of course, not all relationships are strictly linear. How do we get a sense of the curve’s shape? Turns out ggplot will help us find this with a different type of geom_smooth.

# Define the x and y variables
a <- ggplot(elementary_schools, aes(pct_low_inc, parc_cpct))
# Plot points
a + geom_point(color = "turquoise", alpha = .6) +
  geom_smooth(method = "loess")

Do we think a linear model is appropriate here?

That’s not always the case though. Here’s an example from a ProPublica story about redlining in car insurance.

https://www.propublica.org/article/minority-neighborhoods-higher-car-insurance-premiums-methodology

LS0tCnRpdGxlOiAiU3RhdHMgaW4gUiIKYXV0aG9yOiBPbGdhIFBpZXJjZSwgVW5pdmVyc2l0eSBvZiBOZWJyYXNrYS1MaW5jb2xuCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBXaGVuIHlvdSBleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgdGhlIHJlc3VsdHMgYXBwZWFyIGJlbmVhdGggdGhlIGNvZGUuIAoKVGhpcyB3aWxsIGhlbHAgdXMgZm9jdXMgb24gdGhlIGdvYWwgb2YgdGhlIGNvZGUgKHVuZGVyc3RhbmRpbmcgc3RhdHMgYmV0dGVyKSwgaW5zdGVhZCBvZiB0aGUgY29kZSBpdHNlbGYsIHdoaWNoIGlzIGEgbWVhbnMgdG8gYW4gZW5kLgoKWW91IGNhbiB0YWtlIHRoaXMgbm90ZWJvb2sgd2l0aCB5b3UsIHdoaWNoIHdpbGwgcHJvdmlkZSB0ZW1wbGF0ZXMgdG8gZXhlY3V0ZSB2YXJpb3VzIGFuYWx5c2VzLgoKCiMjIyBJbnN0YWxsIHNvbWUgZXh0cmEgcGFja2FnZXMgKGlmIG5lZWRlZCkKCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCJtb2RlbHIiKQppbnN0YWxsLnBhY2thZ2VzKCJlcGlSIikKaW5zdGFsbC5wYWNrYWdlcyAoImphbml0b3IiKQpgYGAKCiMjIyBDYWxsIHBhY2thZ2VzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobW9kZWxyKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KGVwaVIpCmxpYnJhcnkoamFuaXRvcikKYGBgCgojIyMgTG9hZCBkYXRhCgpXZSdsbCBiZSB1c2luZyBhIHRhYmxlIG9mIHNpbXBsaWZpZWQgc2Nob29sLWxldmVsIGRhdGEgcmVnYXJkaW5nIHRoZSByYWNpYWwgbWFrZXVwIG9mIHNjaG9vbHMgYW5kIHRoZWlyIHNjb3JlcyBvbiB0aGUgUEFSQ0Mgc3RhbmRhcmRpemVkIHRlc3QuCgooSXQncyBvdXRzaWRlIG91ciBwdXJ2aWV3IHRvZGF5LCBidXQgb2YgY291cnNlIG5vcm1hbGx5IHdlIHdvdWxkIHVzZSBiZXN0IHByYWN0aWNlIGFuZCBzdHVkeSB0aGUgZGF0YSBkaWN0aW9uYXJ5L2RvY3VtZW50YXRpb24gYmVmb3JlIHdlIGRvIGFueXRoaW5nIGVsc2UpLgoKYGBge3J9CnNjaG9vbHNfZGF0YSA8LSByZWFkX2NzdigiSUxzY2hvb2xzLmNzdiIpCmBgYAoKVGhlIHJlYWRyIHBhY2thZ2UgZGVjaXNpb25zIHNlZW0gcmVhc29uYWJsZS4gTGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlIHRvcCBvZiBvdXIgdGFibGUuIFNjcm9sbCB0aHJvdWdoIHRvIHRoZSByaWdodCwgcGF5aW5nIHNwZWNpYWwgYXR0ZW50aW9uIHRvIG91ciBwZXJjZW50YWdlIGNvbHVtbnMuIAoKYGBge3J9CmhlYWQoc2Nob29sc19kYXRhKQpgYGAKCiMjIyBGaXggY29sdW1uIGhlYWRlcnMKClRoZSBqYW5pdG9yIHBhY2thZ2UgZml4ZXMgYSBsb3Qgb2YgY29tbW9uIGRhdGEgaHlnZWluZSBwcm9ibGVtcy4gSW4gdGhpcyBjYXNlLCB3ZSdsbCBjaGFuZ2UgdGhlc2UgaGlkZW91cyBhbmQgaW5jb25zaXN0ZW50IGNvbHVtbiBuYW1lcyB0byBjb25mb3JtIHdpdGggUiBjb252ZW50aW9ucwoKYGBge3J9CnNjaG9vbHNfZGF0YSA8LSBzY2hvb2xzX2RhdGEgJT4lIGNsZWFuX25hbWVzKCkKaGVhZChzY2hvb2xzX2RhdGEpCmBgYAoKCiMjIyBMZWFybiBhIGJpdCBhYm91dCBvdXIgY29sdW1ucyBmcm9tIHRoZSBnZXQtZ28KCmBgYHtyfQpzdW1tYXJ5KHNjaG9vbHNfZGF0YSkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIHZhcmlvdXMgY29sdW1ucyBoYXZlIHZlcnkgZGlmZmVyZW50IG1lYW5zLiBXZSBjYW4gYWxzbyBzZWUgdGhhdCB0aGUgbWVhbnMgYW5kIG1lZGlhbnMgY2FuIGJlIHZlcnkgZGlmZmVyZW50LiBUaGlzIGluZGljYXRlcyB0byB1cyB0aGF0IHRoZSBkaXN0cmlidXRpb25zIGFyZSBkaWZmZXJlbnQsIGFuZCBub3Qgbm9ybWFsLgoKIyMgRXhwbG9yaW5nIGRpc3RyaWJ1dGlvbnMgd2l0aCBnZ3Bsb3QKCkl0J3MgYSB2ZXJ5IHBvd2VyZnVsIGdyYXBoaWNzIGxpYnJhcnkgaW4gUi4gV2Ugd29uJ3QgZHdlbGwgdG9vIG11Y2ggb24gZ2dwbG90IHN5bnRheCBoZXJlLCBidXQgSSBlbmNvdXJhZ2UgeW91IHRvIGtlZXAgd29ya2luZyBvbiBpdC4KCkZpcnN0LCBsZXQncyBsb29rIGF0IHdoYXQncyBrbm93biBhcyBhIG5vcm1hbCBkaXN0cmlidXRpb24uCgpgYGB7cn0Kc2V0LnNlZWQoMSkKZGYgPC0gZGF0YS5mcmFtZShQRiA9IDEwKnJub3JtKDEwMDAwKSkKZ2dwbG90KGRmLCBhZXMoeCA9IFBGKSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGFlcyh5ID0uLmRlbnNpdHkuLiksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoLTUwLCA1MCwgYnkgPSA1KSwgCiAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLCAKICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiZGVlcHNreWJsdWUiKSArCnN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGFyZ3MgPSBsaXN0KG1lYW4gPSBtZWFuKGRmJFBGKSwgc2QgPSBzZChkZiRQRikpKQpgYGAKCgoKCmBgYHtyfQpwcmludChwYXN0ZTAoIm1lYW46ICIsIHJvdW5kKG1lYW4oZGYkUEYpLDApKSkKcHJpbnQocGFzdGUwKCJtZWRpYW46ICIsIHJvdW5kKG1lZGlhbihkZiRQRiksMCkpKQpgYGAKCkluIHRoaXMgZGlzdHJpYnV0aW9uLCB0aGUgbWVhbiAoYWthICdhdmVyYWdlJykgYW5kIHRoZSBtZWRpYW4gYXJlIGVxdWFsLCBhbmQgYm90aCBzaWRlcyBhcmUgc3ltbWV0cmljYWwgYXJvdW5kIHRoZW0uIEJ1dCB0aGlzIGlzIHVudXN1YWwgaW4gdGhlIHJlYWwgd29ybGQsIHNvIGxvb2tpbmcgYXQgZGlzdHJpYnV0aW9ucyBjYW4gdGVsbCB1cyBhIGxvdCBhYm91dCAKCkxldCdzIHN0YXJ0IHdpdGggdGhlIGRpc3RyaWJ1dGlvbiBvZiB0ZXN0IHNjb3Jlcywgd2hpY2ggd2UnbGwgbWFrZSB3aXRoIGdncGxvdC4gCgpgYGB7cn0KIyBGaXJzdCB0ZWxsIGdncGxvdCB3aGljaCBkYXRhIGFuZCB2YXJpYWJsZSB0byB1c2UKYyA8LSBnZ3Bsb3Qoc2Nob29sc19kYXRhLCBhZXMocGFyY19jcGN0KSkKCiMgVGhlbiB3ZSB0ZWxsIGdncGxvdCB3aGljaCB2aXogdG8gbWFrZQoKYyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTApCmBgYAoKV2hhdCBkb2VzIHRoaXMgdGVsbCB1cyBhYm91dCB0aGUgZGlzdHJpYnV0aW9uIG9mIHNjaG9vbCB0ZXN0LXBhc3NpbmcgcmF0ZXM/IFRoaXMgaXMgY2FsbGVkIHNrZXcuCgpOZXh0IGxldCdzIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcGVyY2VudCBibGFjayB2YXJpYWJsZQoKYGBge3J9CiMgRmlyc3QgdGVsbCBnZ3Bsb3Qgd2hpY2ggZGF0YSBhbmQgdmFyaWFibGUgdG8gdXNlCgpjIDwtIGdncGxvdChzY2hvb2xzX2RhdGEsIGFlcyhwY3RfYmxhY2spKQoKIyBUaGVuIHdlIHRlbGwgZ2dwbG90IHdoaWNoIHZpeiB0byBtYWtlCgpjICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxMCkKYGBgCgpXaGF0IGRvZXMgdGhpcyBzaGFwZSB0ZWxsIHVzIGFib3V0IHNjaG9vbHMgaW4gSWxsaW5vaXM/CgpXaGF0IGFyZSBzb21lIG90aGVyIGFyZWFzIG9mIGxpZmUgd2hlcmUgd2UgbWlnaHQgZmluZCB0aGlzIGRpc3RyaWJ1dGlvbj8KCllvdSB0cnk6IG1ha2UgYSBoaXN0b2dyYW0gb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwY3RfbG93X2luYwoKYGBge3J9CgpgYGAKCiMjIExvb2tpbmcgYXQgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHR3byB2YXJpYWJsZXMKCkxldCdzIGV4cGxvcmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBwZXJjZW50IG9mIGEgc2Nob29sJ3Mgc3R1ZGVudHMgd2hvIGFyZSBsb3ctaW5jb21lLCBhbmQgdGhlIHBlcmNlbnQgd2hvIGFyZSBwcm9maWNpZW50LgoKRmlyc3QsIGxldCdzIHByYWN0aWNlIGZpbHRlcmluZyBmb3IganVzdCBlbGVtZW50YXJ5IHNjaG9vbHMuCgpgYGB7cn0KZWxlbWVudGFyeV9zY2hvb2xzIDwtIApgYGAKCk5vdyBsZXQncyBwbG90IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgcGVyY2VudCBvZiBhIHNjaG9vbCdzIHN0dWRlbnRzIHdobyBhcmUgbG93LWluY29tZSBhbmQgdGhlIHBlcmNlbnQgd2hvIGFjaGlldmVkIHByb2ZpY2llbmN5IG9uIHRoZSBQQVJDQyBleGFtLgoKYGBge3J9CiMgRGVmaW5lIHRoZSB4IGFuZCB5IHZhcmlhYmxlcwphIDwtIGdncGxvdChlbGVtZW50YXJ5X3NjaG9vbHMsIGFlcyhwY3RfbG93X2luYywgcGFyY19jcGN0KSkKCiMgUGxvdCBwb2ludHMKYSArIGdlb21fcG9pbnQoY29sb3IgPSAidHVycXVvaXNlIiwgYWxwaGEgPSAuNikKYGBgCgpXZSBjYW4gc2VlIHRoYXQgaW4gZ2VuZXJhbCBhIGhpZ2hlciByYXRlIG9mIGxvdy1pbmNvbWUgc3R1ZGVudHMgYXQgYSBzY2hvb2wgaXMgYXNzb2NpYXRlZCB3aXRoIGEgbG93ZXIgcmF0ZSBvZiBwYXNzaW5nIHRoZSBleGFtLgoKTGV0J3MgZm9ybWFsaXplIHRoaXMgYnkgZml0dGluZyBhIGxpbmUuCgpgYGB7cn0KYSArIGdlb21fcG9pbnQoY29sb3IgPSAidHVycXVvaXNlIiwgYWxwaGEgPSAuNikgKyBnZW9tX3Ntb290aChtZXRob2QgPSBsbSkKYGBgCgpgYGB7cn0KbG93X2luY19tb2QgPC0gbG0ocGFyY19jcGN0IH4gcGN0X2xvd19pbmMsIGRhdGEgPSBlbGVtZW50YXJ5X3NjaG9vbHMpCnN1bW1hcnkobG93X2luY19tb2QpCmBgYAoKUmVtZW1iZXIgeSA9IG14ICsgYiA/IFdoYXQgZG9lcyB0aGUgb3V0cHV0IGFib3ZlIHRlbGwgdXM/CgpZb3UgdHJ5OiBSdW4gYSBsaW5lYXIgcmVncmVzc2lvbiB0aGF0IHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgcGVyY2VudCB3aGl0ZSBhbmQgdGhlIHBlcmNlbnQgd2hvIHBhc3NlZCB0aGUgZXhhbS4gV2hhdCBkbyB3ZSBsZWFybiBmcm9tIHRoaXM/IEhvdyBkbyB3ZSBwdXQgaXQgaW50byB3b3Jkcz8KCiMjIyBNdWx0aXBsZSByZWdyZXNzaW9uCgpEb2VzIGEgc2Nob29sIGJlaW5nIGluIENoaWNhZ28gYWZmZWN0IHRoZXNlIHJlc3VsdHM/IEZpcnN0LCBsZXQncyBjb25zaWRlciB0aGlzIHZpc3VhbGx5LiAKCmBgYHtyfQphIDwtIGdncGxvdChlbGVtZW50YXJ5X3NjaG9vbHMsIGFlcyhwY3RfbG93X2luYywgcGFyY19jcGN0LCBjb2xvciA9IGNoaSkpCmEgKyBnZW9tX3BvaW50KGFscGhhID0gLjUpCmBgYAoKYGBge3J9CmEgKyBnZW9tX3BvaW50KGFscGhhID0gLjUpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCgpMZXQncyBydW4gYSBuZXcgcmVncmVzc2lvbiB0aGF0IGluY2x1ZGVzIHRoZSAnY2hpJyB2YXJpYWJsZS4gQW5kIHRoZW4gc3VtbWFyaXplIG91dHB1dC4KCmBgYHtyfQptdWx0aV9tb2QgPC0gbG0ocGFyY19jcGN0IH4gcGN0X2xvd19pbmMgKyBjaGksIGRhdGEgPSBlbGVtZW50YXJ5X3NjaG9vbHMpCnN1bW1hcnkobXVsdGlfbW9kKQpgYGAKCkxvb2tpbmcgYmV0dGVyLCBidXQgdGhlIG91dHB1dCBpcyBzdGlsbCBwcmV0dHkgdWdseS4gV2UgY2FuIG1ha2UgaXQgbW9yZSB1c2VmdWwgdXNpbmcgdGhlIGJyb29tIHBhY2thYWdlLgoKYGBge3J9CnRpZHkobXVsdGlfbW9kKQpgYGAKCk5vdyBpdCdzIGEgZGF0YSBmcmFtZSwgaW5zdGVhZCBvZiB0aGF0IHdlaXJkIG1vZGVsIG9iamVjdC4KCllvdSBwcmFjdGljZS4gUnVuIGEgbGluZWFyIG1vZGVsIHdoZXJlIG91ciBwcmVkaWN0b3JzIGFyZSBwY3Rfd2hpdGUgYW5kIGNoaS4gVGhlbiBjbGVhbiB1cCB5b3VyIG91dHB1dCB1c2luZyBicm9vbS4KCmBgYHtyfQoKYGBgCgpJZiB3ZSBmZWVkIG91ciBvcmlnaW5hbCBkYXRhIGZyYW1lIGFuZCBvdXIgbW9kZWwgdGhyb3VnaCB0aGUgYXVnbWVudCgpIGZ1bmN0aW9uLCBpdCB3aWxsIGNhbGN1bGF0ZSBmaXR0ZWQgdmFsdWVzIGZvciB1cy4KCkEgZml0dGVkLCBvciBwcmVkaWN0ZWQsIHZhbHVlIGlzIHRoZSBwZXJjZW50IFBBUkNDIHByb2ZpY2llbmN5IG91ciBtb2RlbCBzdWdnZXN0cyB3b3VsZCBiZSBleHBlY3RlZCBmb3IgdGhpcyBzY2hvb2wgZ2l2ZW4gdGhlIHRlcm1zIGluIG91ciBtb2RlbCAoaW4gdGhpcyBjYXNlIHRoZSBwZXJjZW50IG9mIGEgc2Nob29sJ3Mgc3R1ZGVudHMgd2hvIGFyZSBsb3ctaW5jb21lIGFuZCB3aGV0aGVyIHRoZSBzY2hvb2wgaXMgaW4gQ2hpY2FnbykuCgpgYGB7cn0KbW9kX2dyaWQgPC0gbG0ocGFyY19jcGN0IH4gcGN0X2xvd19pbmMgKyBjaGksIGRhdGEgPSBzY2hvb2xzX2RhdGEsIG5hLmFjdGlvbiA9ICJuYS5leGNsdWRlIikgJT4lIGF1Z21lbnQobXVsdGlfbW9kLCBlbGVtZW50YXJ5X3NjaG9vbHMpIApWaWV3KGhlYWQobW9kX2dyaWQsMjApKQpgYGAKClRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGZpdHRlZCB2YWx1ZSBhbmQgdGhlIGFjdHVhbCB2YWx1ZSBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgd2hldGhlciBhIHNjaG9vbCBpcyBvdmVyLSBvciB1bmRlci1wZXJmb3JtaW5nLiBXZSBjYW4gcGxvdCB0aGlzLgoKVG8ga2VlcCBvdXIgcGxvdCBmcm9tIGJlaW5nIHRvbyBidXN5LCBsZXQncyBmaWx0ZXIgb3V0IHZhbHVlcyB0aGF0IGFyZSBub3Qgb3V0bGllcnMuCgpgYGB7cn0KbW9kX2dyaWQgPC0gbW9kX2dyaWQgJT4lIG11dGF0ZShyZXNpZHVhbCA9IC5maXR0ZWQgLSBwYXJjX2NwY3QpICU+JSBmaWx0ZXIocmVzaWR1YWwgPCAtMjAgfCByZXNpZHVhbCA+IDIwKQpgYGAKCgoKYGBge3J9CmdncGxvdChtb2RfZ3JpZCwgYWVzKHggPSBwY3RfbG93X2luYywgeSA9IHBhcmNfY3BjdCkpICsKICBnZW9tX3NlZ21lbnQoYWVzKHhlbmQgPSBwY3RfbG93X2luYywgeWVuZCA9IC5maXR0ZWQpLCBhbHBoYSA9IC4yKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSByZXNpZHVhbCksYWxwaGEgPSAuOCkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIikgKwogIGd1aWRlcyhjb2xvciA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IC5maXR0ZWQpKSArCiAgZmFjZXRfZ3JpZCh+IGNoaSkKYGBgCgojIExvb2tzIGxpa2Ugd2UndmUgZ290IHNvbWUgc2Nob29scyB3aXRoIHVuZXhwZWN0ZWQgdGVzdCBzY29yZXMhCgpMZXQncyBmaW5kIHRoZW0uCgpgYGB7cn0KVmlldyhtb2RfZ3JpZCAlPiUgYXJyYW5nZShyZXNpZHVhbCkpCmBgYAoKU3RvcnkgbGFuZ3VhZ2U6CgpFaXNlbmhvd2VyIEVsZW1lbnRhcnkgb3V0c2NvcmVkIHdoYXQgd291bGQgYmUgZXhwZWN0ZWQgZm9yIGEgc2ltaWxhciBzY2hvb2wgd2l0aCBtb3N0bHkgbG93LWluY29tZSBzdHVkZW50cyBieSA3MyBwZXJjZW50YWdlIHBvaW50cy4KCiMjIyBMb2dpc3RpYyByZWdyZXNzaW9uCgpBZGQgYSBjb2x1bW4gZm9yICdnb29kIHNjaG9vbCcgd2hlcmUgbW9yZSB0aGFuIGhhbGYgb2YgdGhlIHN0dWRlbnRzIGhhdmUgcGFzc2VkIHRoZSB0ZXN0LgoKYGBge3J9CgpgYGAKCk5vdyBsZXQncyB1c2UgbW9yZSB0aGFuIG9uZSB2YXJpYWJsZSB0byBwcmVkaWN0IHdoZXRoZXIgYSBzY2hvb2wgaXMgImdvb2QiLgoKYGBge3J9CgpgYGAKCldlIGNhbiB1c2Ugb3VyIHN1bW1hcnkgZnVuY3Rpb24gYWdhaW4gdG8gc2VlIHRoZSBvdXRwdXQgb2Ygb3VyIHJlZ3Jlc3Npb24uCgpgYGB7cn0KYGBgCgpMZXQncyB1bnBhY2sgd2hhdCBvdXIgcmVzdWx0cyBtZWFuLgoKWW91IHRyeTogTGV0J3MgY3JlYXRlIGEgdmFyaWFibGUgaW5kaWNhdGluZyB3aGV0aGVyIGEgc2Nob29sIGlzIGluIENvb2sgQ291bnR5IG9yIG5vdCwgY2FsbGVkICdjb29rJy4KCmBgYHtyfQoKYGBgCgpOb3cgbGV0J3MgcnVuIGEgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIGNvb2sgYW5kIHBjdF9ibGFjayBhcyBvdXIgcHJlZGljdG9ycyBhbmQgZ29vZF9zY2hvb2wgYXMgb3VyIGRlcGVuZGVudCB2YXJpYWJsZSwgdGhlbiBzdW1tYXJpemUuCgpgYGB7cn0KCmBgYAoKV2hhdCBkbyB3ZSBsZWFybj8KCllvdSB0cnk6IFJ1biBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBjaGkgYW5kIHBjdF93aGl0ZSBhcyBvdXIgcHJlZGljdG9ycywgYW5kIGdvb2Rfc2Nob29sIGFzIG91ciBkZXBlbmRlbnQgdmFyaWFibGUuIFRoZW4gc3VtbWFyaXplLgoKYGBge3J9CgpgYGAKCldoYXQgZG8gd2UgbGVhcm4/CgpXaGF0IGFyZSBzb21lIG90aGVyIHF1ZXN0aW9ucyB3ZSBjb3VsZCBhbnN3ZXIgd2l0aCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24/CgpgYGB7cn0KCmBgYAoKTm9uLWxpbmVhciByZWxhdGlvbnNoaXBzCgpPZiBjb3Vyc2UsIG5vdCBhbGwgcmVsYXRpb25zaGlwcyBhcmUgc3RyaWN0bHkgbGluZWFyLiBIb3cgZG8gd2UgZ2V0IGEgc2Vuc2Ugb2YgdGhlIGN1cnZlJ3Mgc2hhcGU/IFR1cm5zIG91dCBnZ3Bsb3Qgd2lsbCBoZWxwIHVzIGZpbmQgdGhpcyB3aXRoIGEgZGlmZmVyZW50IHR5cGUgb2YgZ2VvbV9zbW9vdGguIAoKYGBge3J9CiMgRGVmaW5lIHRoZSB4IGFuZCB5IHZhcmlhYmxlcwphIDwtIGdncGxvdChlbGVtZW50YXJ5X3NjaG9vbHMsIGFlcyhwY3RfbG93X2luYywgcGFyY19jcGN0KSkKCiMgUGxvdCBwb2ludHMKYSArIGdlb21fcG9pbnQoY29sb3IgPSAidHVycXVvaXNlIiwgYWxwaGEgPSAuNikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIpCmBgYAoKRG8gd2UgdGhpbmsgYSBsaW5lYXIgbW9kZWwgaXMgYXBwcm9wcmlhdGUgaGVyZT8KClRoYXQncyBub3QgYWx3YXlzIHRoZSBjYXNlIHRob3VnaC4gSGVyZSdzIGFuIGV4YW1wbGUgZnJvbSBhIFByb1B1YmxpY2Egc3RvcnkgYWJvdXQgcmVkbGluaW5nIGluIGNhciBpbnN1cmFuY2UuCgpodHRwczovL3d3dy5wcm9wdWJsaWNhLm9yZy9hcnRpY2xlL21pbm9yaXR5LW5laWdoYm9yaG9vZHMtaGlnaGVyLWNhci1pbnN1cmFuY2UtcHJlbWl1bXMtbWV0aG9kb2xvZ3kKCgoKCgoKCgo=